import moment, { Moment } from 'moment';
import { Subject, Observable } from 'rxjs';
import { getAutoPrimaryKey } from '@/utils/utils';
import { calendarUtil } from '@/utils/calendar-util/calendar-util';
import { IBzCalendarController } from './ibz-calendar-controller';
import { IBzCalendarConfig } from '../interface/ibz-calendar-config';
import { IBzCalendarLayoutConfig } from '../interface/ibz-calendar-layout-config';
import { IBzCalendarWeekNode } from '../interface/ibz-calendar-week-node';

/**
 * 日历周布局模式控制器
 *
 * @export
 * @class IBzCalendarWeekController
 * @extends {IBzCalendarController}
 */
export class IBzCalendarWeekController extends IBzCalendarController {

    /**
     * 某节点变更数据流
     *
     * @protected
     * @type {Subject<IBzCalendarWeekNode>}
     * @memberof IBzCalendarWeekController
     */
    protected nodeChangeSubject: Subject<IBzCalendarWeekNode> = new Subject();

    /**
     * 某「天」数据发生变化
     *
     * @protected
     * @type {Subject<Moment>}
     * @memberof IBzCalendarWeekController
     */
    protected dayItemsChangeSubject: Subject<Moment> = new Subject();

    /**
     * 「周」视图实例
     *
     * @protected
     * @type {*}
     * @memberof IBzCalendarWeekController
     */
    protected weekViewInstance: any;

    /**
     * 布局配置
     *
     * @type {IBzCalendarLayoutConfig}
     * @memberof IBzCalendarWeekController
     */
    public layoutConfig: IBzCalendarLayoutConfig = {
        // 默认行高
        lineHeight: 40,
        // 默认单次拖拽偏移量
        dragOffset: 10
    };

    /**
     * 小时标度列表
     *
     * @type {string []}
     * @memberof IBzCalendarWeekController
     */
    public hourSplits: string[] = [];

    /**
     * 时间标度列表
     *
     * @type {{ text: string, date: string }[]}
     * @memberof IBzCalendarWeekController
     */
    public dateSplits: { week: string, month: string, day: string, date: string }[] = [];

    /**
     * 所有节点
     *
     * @type {IBzCalendarWeekNode[]}
     * @memberof IBzCalendarWeekController
     */
    public nodes: IBzCalendarWeekNode[] = [];

    /**
     * 缓存计算过的天内数据
     *
     * @type {Map<string, IBzCalendarWeekNode[]>}
     * @memberof IBzCalendarWeekController
     */
    public dayItemsMap: Map<string, IBzCalendarWeekNode[]> = new Map();

    /**
     * 设置参数
     *
     * @param {IBzCalendarConfig} opt
     * @memberof IBzCalendarWeekController
     */
    public setOption(opt: IBzCalendarConfig): void {
        super.setOption(opt);
        this.calcHourSplitList();
        this.calcDaySplitList();
    }

    /**
     * 初始化配置
     *
     * @protected
     * @param {IBzCalendarConfig} opt
     * @returns {IBzCalendarConfig}
     * @memberof IBzCalendarWeekController
     */
    protected initOption(opt: IBzCalendarConfig): IBzCalendarConfig {
        const config: IBzCalendarConfig = super.initOption(opt);
        // 拖拽单次跨度时间，默认为30分钟
        config.dragOffsetMinutes = opt.dragOffsetMinutes || 30;
        // 指定跨度天数，默认7天
        config.spanDay = opt.spanDay || 7;
        // 起始日期为String时转换为Moment
        if (opt.startDate && typeof opt.startDate === 'string') {
            config.startDate = moment(opt.startDate);
        } else {
            // 日历起始日期，默认为当前时间
            config.startDate = opt.startDate || moment(new Date());
        }
        // 一天起始时间偏移
        config.timeDeviation = opt.timeDeviation || 0;
        // 结束时间为String时转换为Moment
        if (opt.endDate && typeof opt.endDate === 'string') {
            config.endDate = moment(opt.endDate);
        } else {
            // 日历结束日期，给予值优先，默认使用跨度天数算
            config.endDate = opt.endDate || config.startDate.clone().add(opt.spanDay - 1, 'days');
        }
        // 结束时间已指定，计算跨度天数
        if (opt.endDate) {
            config.spanDay = Math.abs(moment(config.startDate).diff(moment(config.endDate), 'days')) + 2;
        }
        // 单次拖拽跨度「值必须为整数」 = 单行高度 ÷ (单行时间分割标度 ÷ 单次拖拽跨度时间)
        config.layoutConfig.dragOffset = config.layoutConfig.lineHeight / (config.timeSplitScale / config.dragOffsetMinutes);
        return config;
    }

    /**
     * 计算小时标度列表
     *
     * @protected
     * @memberof IBzCalendarWeekController
     */
    protected calcHourSplitList(): void {
        this.hourSplits.splice(0, this.hourSplits.length);
        const m = this.calcDayStartDate('2020-02-20');
        this.hourSplits.push(m.format('HH:mm:ss'));
        // 一天总时间数，除以分时标度，计算总共有多少行
        const num = 1440 / this.option.timeSplitScale;
        for (let i = 1; i < num; i++) {
            // 拼接时间字符串
            this.hourSplits.push(m.add(this.option.timeSplitScale, 'minutes').format('HH:mm'));
        }
    }

    /**
     * 计算天标度列表
     *
     * @protected
     * @memberof IBzCalendarWeekController
     */
    protected calcDaySplitList(): void {
        this.dateSplits.splice(0, this.dateSplits.length);
        for (let i = 0; i < this.option.spanDay; i++) {
            const date = this.option.startDate.clone().add(i, 'days');
            this.dateSplits.push({
                week: calendarUtil.getWeekText(date.weekday()),
                month: date.format('M'),
                day: date.format('D'),
                date: date.format('YYYY-MM-DD')
            });
        }
    }

    /**
     * 设置数据
     *
     * @param {any[]} items
     * @memberof IBzCalendarWeekController
     */
    public setItems(items: any[]): void {
        this.nodes = [];
        this.dayItemsMap.clear();
        super.setItems(items);
        items.forEach((item: any) => {
            const data = this.formatNodeData(item);
            this.nodes.push(data);
        });
    }

    /**
     * 计算节点内容
     *
     * @protected
     * @param {*} item
     * @returns {IBzCalendarWeekNode}
     * @memberof IBzCalendarWeekController
     */
    protected formatNodeData(item: any): IBzCalendarWeekNode {
        return {
            id: getAutoPrimaryKey(),
            text: item[this.option.textKey] || '',
            dragging: false,
            startMoment: moment(new Date(item[this.option.startTimeKey])),
            endMoment: moment(new Date(item[this.option.endTimeKey])),
            data: item
        };
    }

    /**
     * 返回指定日期数据
     *
     * @param {string} date 指定日期。例：2020-02-20
     * @returns {IBzCalendarWeekNode[]}
     * @memberof IBzCalendarWeekController
     */
    public getSpecifyDateItems(date: string): IBzCalendarWeekNode[] {
        const items: IBzCalendarWeekNode[] = [];
        const start = this.calcDayStartDate(date);
        const end = this.calcDayEndDate(date);
        this.nodes.forEach((item: IBzCalendarWeekNode) => {
            if (calendarUtil.isOverlapsTime(start, end, item.startMoment, item.endMoment)) {
                items.push(item);
            }
        });
        items.sort((a, b) => {
            return a.startMoment.isBefore(b.startMoment) ? -1 : 1;
        });
        this.dayItemsMap.set(this.calcDayKey(start, end), items);
        return items;
    }

    /**
     * 变更数据起始和结束时间
     *
     * @param {Moment} newStartMoment 新的起始时间, Moment格式
     * @param {Moment} newEndMoment 新的结束时间，Moment格式
     * @param {IBzCalendarWeekNode} item 需要操作的数据
     * @memberof IBzCalendarWeekController
     */
    public changeNodeStartAndEndDate(newStartMoment: Moment, newEndMoment: Moment, item: IBzCalendarWeekNode): void {
        this.changeNodeStartDate(newStartMoment, item);
        this.changeNodeEndDate(newEndMoment, item);
    }

    /**
     * 变更数据起始时间
     *
     * @param {Moment} newStartMoment 新的起始时间, Moment格式
     * @param {IBzCalendarWeekNode} item 需要操作的数据
     * @memberof IBzCalendarWeekController
     */
    public changeNodeStartDate(newStartMoment: Moment, item: IBzCalendarWeekNode): void {
        if (newStartMoment && item) {
            item.startMoment = newStartMoment;
            item.data[this.option.startTimeKey] = newStartMoment.format(this.option.dateFormat);
        }
    }

    /**
     * 变更数据结束时间
     *
     * @param {Moment} newEndMoment 新的结束时间，Moment格式
     * @param {IBzCalendarWeekNode} item 需要操作的数据
     * @memberof IBzCalendarWeekController
     */
    public changeNodeEndDate(newEndMoment: Moment, item: IBzCalendarWeekNode): void {
        if (newEndMoment && item) {
            item.endMoment = newEndMoment;
            item.data[this.option.endTimeKey] = newEndMoment.format(this.option.dateFormat);
        }
    }

    /**
     * 设置「周」视图容器
     *
     * @param {*} instance
     * @memberof IBzCalendarWeekController
     */
    public setWeekView(instance: any): void {
        this.weekViewInstance = instance;
    }

    /**
     * 所有日期，重新计算当日数据
     *
     * @memberof IBzCalendarWeekController
     */
    public allRecalculateItems(): void {
        if (this.weekViewInstance) {
            this.weekViewInstance.changeRecalculateCount();
        }
    }

    /**
     * 「周」视图强制脏检查更新
     *
     * @memberof IBzCalendarWeekController
     */
    public weekViewTick(): void {
        if (this.weekViewInstance) {
            this.weekViewInstance.tick();
        }
    }

    /**
     * 发射节点变更通知
     *
     * @param {IBzCalendarWeekNode} item
     * @memberof IBzCalendarWeekController
     */
    public nodeNext(item: IBzCalendarWeekNode): void {
        this.nodeChangeSubject.next(item);
    }

    /**
     * 订阅节点变更通知
     *
     * @returns {Observable<IBzCalendarWeekNode>}
     * @memberof IBzCalendarWeekController
     */
    public changeNodeObservable(): Observable<IBzCalendarWeekNode> {
        return this.nodeChangeSubject.asObservable();
    }

    /**
     * 推送某天数据变化
     *
     * @param {Moment} date 日期，例：2020-02-20 00:00:00_202-02-20 23:59:59
     * @memberof IBzCalendarWeekController
     */
    public dayChange(dateKey: Moment): void {
        this.dayItemsChangeSubject.next(dateKey);
    }

    /**
     * 订阅天数据变化
     *
     * @returns {Observable<Moment>}
     * @memberof IBzCalendarWeekController
     */
    public changeDayItemsObservable(): Observable<Moment> {
        return this.dayItemsChangeSubject.asObservable();
    }

    /**
     * 时间是否在某天冲突
     *
     * @param {IBzCalendarWeekNode} self
     * @param {Moment} [newStartMoment]
     * @param {Moment} [newEndMoment]
     * @returns {boolean}
     * @memberof IBzCalendarWeekController
     */
    public isOverlapsTodayTime(self: IBzCalendarWeekNode, start: Moment, end: Moment, newStartMoment?: Moment, newEndMoment?: Moment): boolean {
        const items = this.dayItemsMap.get(this.calcDayKey(start, end));
        return this.isOverlapsTime(self, items, newStartMoment, newEndMoment);
    }

    /**
     * 是否出现时间重叠
     *
     * @protected
     * @param {IBzCalendarWeekNode} self
     * @param {IBzCalendarWeekNode[]} items
     * @param {Moment} newStartMoment
     * @param {Moment} newEndMoment
     * @returns {boolean}
     * @memberof IBzCalendarWeekController
     */
    protected isOverlapsTime(self: IBzCalendarWeekNode, items: IBzCalendarWeekNode[], newStartMoment: Moment, newEndMoment: Moment): boolean {
        let judge = false;
        items.findIndex((item) => {
            if (item.id === self.id) {
                return;
            }
            judge = calendarUtil.isOverlapsTime(newStartMoment, newEndMoment, item.startMoment, item.endMoment);
            return judge;
        });
        return judge;
    }

    /**
     * 计算日起始时间
     *
     * @param {string} date 日期
     * @returns {Moment}
     * @memberof IBzCalendarWeekController
     */
    public calcDayStartDate(date: string): Moment {
        return calendarUtil.getDayStartDate(date, this.option.timeDeviation);
    }

    /**
     * 计算日结束时间
     *
     * @param {string} date
     * @returns {Moment}
     * @memberof IBzCalendarWeekController
     */
    public calcDayEndDate(date: string): Moment {
        return calendarUtil.getDayEndDate(date, this.option.timeDeviation);
    }

}